package com.lhz.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.config.AlipayConfig;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: LiHuaZhi
 * @Date: 2021/11/14 18:29
 * @Description:
 **/
@Controller
@Slf4j
public class TestController {

    private final String TRADE_FINISHED = "TRADE_FINISHED";
    private final String TRADE_SUCCESS = "TRADE_SUCCESS";

    @Value("${alipay.app_id}")
    private String notifyUrl;

    @Value("${alipay.app_id}")
    private String returnUrl;

    @Value("${alipay.charset}")
    private String charset;

    @Value("${alipay.alipay_public_key}")
    private String alipayPublicKey;

    @Value("${alipay.sign_type}")
    private String signType;

    @Resource
    private AlipayClient alipayClient;

    /**
     * 服务器异步通知页面路径，接收支付宝回调后，再封装页面数据，直接返回相应页面到前端
     *
     * @param request
     * @param response
     * @throws IOException
     * @deprecated API地址：https://opendocs.alipay.com/open/203/105286
     */
    @PostMapping("/notify")
    public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("异步通知");
        PrintWriter out = response.getWriter();
        //乱码解决，这段代码在出现乱码时使用
        request.setCharacterEncoding("utf-8");
        //获取支付宝POST过来反馈信息
        Map<String, String> params = new HashMap<>(8);
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
            String[] values = stringEntry.getValue();
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(stringEntry.getKey(), valueStr);
        }

        //调用SDK验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType);

        if (!signVerified) {
            log.error("验签失败");
            out.print("fail");
            return;
        }

        //商户订单号,之前生成的带用户ID的订单号
        String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        //支付宝交易号
        String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        //付款金额
        String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        //交易状态
        String tradeStatus = new String(params.get("trade_status").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

        /*
         * 交易状态
         * TRADE_SUCCESS 交易完成
         * TRADE_FINISHED 支付成功
         * WAIT_BUYER_PAY 交易创建
         * TRADE_CLOSED 交易关闭
         */
        log.info("tradeStatus:" + tradeStatus);

        if (tradeStatus.equals(TRADE_FINISHED)) {
            /*此处可自由发挥*/
            //判断该笔订单是否在商户网站中已经做过处理
            //如果没有做过处理，根据订单号（out_trade_no）在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
            //如果有做过处理，不执行商户的业务程序
            //注意：
            //退款日期超过可退款期限后（如三个月可退款），支付宝系统发送该交易状态通知
        } else if (tradeStatus.equals(TRADE_SUCCESS)) {
            //判断该笔订单是否在商户网站中已经做过处理
            //如果没有做过处理，根据订单号（out_trade_no在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
            //如果有做过处理，不执行商户的业务程序

            // 此处代表交易已经成果，编写实际页面代码
            // 比如：重置成功，那么往数据库中写入重置金额
            log.info("trade_no:" + tradeNo);
            log.info("outTradeNo:" + outTradeNo);
            log.info("totalAmount:" + totalAmount);
        }

        out.print("success");
    }

    /**
     * 完成支付后的同步通知
     * 页面跳转同步通知页面路径，接收支付宝回调后，再封装页面数据，直接返回相应页面到前端
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/return")
    public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("同步通知");

        //乱码解决，这段代码在出现乱码时使用
        request.setCharacterEncoding("utf-8");

        //获取支付宝GET过来反馈信息
        Map<String, String> params = new HashMap<>(8);
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
            String[] values = stringEntry.getValue();
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(stringEntry.getKey(), valueStr);
        }

        //商户订单号,之前生成的带用户ID的订单号
        String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        //支付宝交易号
        String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        //付款金额
        String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

        log.info("trade_no:" + tradeNo);
        log.info("outTradeNo:" + outTradeNo);
        log.info("totalAmount:" + totalAmount);

        //调用SDK验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType);

        if (signVerified) {
            System.out.println("验签成功-跳转到成功后页面");
            //跳转至支付成功后的页面,
            return "redirect:payment";
        } else {
            System.out.println("验签失败-跳转到充值页面让用户重新充值");
            return "redirect:false";
        }
    }

    /**
     * 下单交易接口
     *
     * @param httpRequest
     * @param httpResponse
     * @throws IOException
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbs?scene=21
     */
    @ApiOperation(value = "支付订单", notes = "支付订单")
    @ApiOperationSupport(order = 1)
    @GetMapping("/pay")
    public void pay(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception {

        long outTradeNo = System.currentTimeMillis();
        log.info("支付订单，订单号：" + outTradeNo);

        // 相应请求API的request
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
        // 在公共参数中设置回跳和通知地址
        alipayRequest.setReturnUrl(returnUrl);
        alipayRequest.setNotifyUrl(notifyUrl);

        // 封装商品信息
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", outTradeNo);
        bizContent.put("total_amount", 0.01);
        bizContent.put("subject", "测试商品");
        // 沙箱默认值为：QUICK_WAP_WAY
        bizContent.put("product_code", "QUICK_WAP_WAY");
        //bizContent.put("time_expire", "2022-08-01 22:00:00");

        //// 商品明细信息，按需传入
        //JSONArray goodsDetail = new JSONArray();
        //JSONObject goods1 = new JSONObject();
        //goods1.put("goods_id", "goodsNo1");
        //goods1.put("goods_name", "子商品1");
        //goods1.put("quantity", 1);
        //goods1.put("price", 0.01);
        //goodsDetail.add(goods1);
        //bizContent.put("goods_detail", goodsDetail);

        //// 扩展信息，按需传入
        //JSONObject extendParams = new JSONObject();
        //extendParams.put("sys_service_provider_id", "2088511833207846");
        //bizContent.put("extend_params", extendParams);

        alipayRequest.setBizContent(bizContent.toString());

        //调用SDK生成表单
        String form = "";
        try {
            form = alipayClient.pageExecute(alipayRequest).getBody();
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        httpResponse.setContentType("text/html;charset=" + charset);
        //直接将完整的表单html输出到页面
        httpResponse.getWriter().write(form);
        httpResponse.getWriter().flush();
        httpResponse.getWriter().close();
    }

    /**
     * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbt
     */
    @ApiOperation(value = "查询订单", notes = "查询订单")
    @ApiOperationSupport(order = 2)
    @GetMapping("/query/{tradeNum}")
    @ResponseBody
    public Object query(@PathVariable("tradeNum") String tradeNum) throws Exception {
        //创建API对应的request类
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        // 商户网站唯一订单号
        bizContent.put("out_trade_no", tradeNum);
        // 支付宝交易号
        //bizContent.put("trade_no", tradeNum);
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = alipayClient.execute(request);

        // 打印结果
        log.debug(response.getBody());

        // 根据response中的结果继续业务逻辑处理
        if (response.isSuccess()) {
            log.info("调用支付宝成功");
            log.info(response.getSubMsg());
            /**
             * 获取状态交易状态：
             * WAIT_BUYER_PAY（交易创建，等待买家付款）
             * TRADE_CLOSED（未付款交易超时关闭，或支付完成后全额退款）
             * TRADE_SUCCESS（交易支付成功）
             * TRADE_FINISHED（交易结束，不可退款）
             */
            String tradeStatus = response.getTradeStatus();
            log.info("交易状态：" + tradeStatus);

            log.info("支付宝交易号：" + response.getTradeNo());
            log.info("商家订单号：" + response.getOutTradeNo());
            log.info("买家支付宝账号：" + response.getBuyerLogonId());
            log.info("买家在支付宝的用户id：" + response.getBuyerUserId());
            log.info("买家在支付宝的用户id：" + response.getBuyerUserId());
        } else {
            log.error("调用支付宝失败");
            log.error(response.getSubCode());
            log.error(response.getSubMsg());
        }
        return "调用查询订单";
    }


    /**
     * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
     * @return
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbu
     */
    @ApiOperation(value = "关闭订单", notes = "关闭订单")
    @ApiOperationSupport(order = 2)
    @PostMapping("/close/{tradeNum}")
    @ResponseBody
    public Object close(@PathVariable("tradeNum") String tradeNum) throws Exception {
        //创建API对应的request类
        AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
        JSONObject bizContent = new JSONObject();
        // 商户网站唯一订单号
        bizContent.put("out_trade_no", tradeNum);
        // 支付宝交易号
        //bizContent.put("trade_no", tradeNum);
        request.setBizContent(bizContent.toString());
        AlipayTradeCloseResponse response = alipayClient.execute(request);

        // 打印结果
        log.debug(response.getBody());

        // 根据response中的结果继续业务逻辑处理
        if (response.isSuccess()) {
            log.info("调用支付宝成功");
            log.info(response.getSubMsg());

            log.info("支付宝交易号：" + response.getTradeNo());
            log.info("商家订单号：" + response.getOutTradeNo());
        } else {
            log.error("调用支付宝失败");
            log.error(response.getSubCode());
            log.error(response.getSubMsg());
        }
        return "调用关闭订单";
    }

    /**
     * @return
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbx
     */
    @ApiOperation(value = "订单退款", notes = "订单退款")
    @ApiOperationSupport(order = 2)
    @GetMapping("/refund/{tradeNum}")
    @ResponseBody
    public Object refund(@PathVariable("tradeNum") String tradeNum) throws Exception {
        //创建API对应的request类
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
        JSONObject bizContent = new JSONObject();
        // 商户网站唯一订单号
        bizContent.put("out_trade_no", tradeNum);
        // 支付宝交易号
//        bizContent.put("trade_no", "2021081722001419121412730660");
        // 退款金额
        bizContent.put("refund_amount", 0.01);
        // 退款原因
        bizContent.put("refund_reason", "申请退款");

        // 退款请求号,标识一次退款请求，需要保证在交易号下唯一，如需部分退款，则此参数必传。
        // 针对同一次退款请求，如果调用接口失败或异常了，重试时需要保证退款请求号不能变更。
        bizContent.put("out_request_no", "1122334455");

        request.setBizContent(bizContent.toString());
        AlipayTradeRefundResponse response = alipayClient.execute(request);

        // 打印结果
        log.debug(response.getBody());

        // 根据response中的结果继续业务逻辑处理
        if (response.isSuccess()) {
            log.info("调用支付宝成功");
            log.info(response.getSubMsg());

            /**
             * 本次退款是否发生了资金变化
             * Y 表示退款成功
             */
            String fundChange = response.getFundChange();

            log.info("支付宝交易号：" + response.getTradeNo());
            log.info("商家订单号：" + response.getOutTradeNo());
            log.info("已退款的总金额：" + response.getRefundFee());
            log.info("买家支付宝账号：" + response.getBuyerLogonId());
            log.info("买家在支付宝的用户id：" + response.getBuyerUserId());
            log.info("买家在支付宝的用户id：" + response.getBuyerUserId());
        } else {
            log.error("调用支付宝失败");
            log.error(response.getSubCode());
            log.error(response.getSubMsg());
        }
        return "调用订单退款";
    }

    /**
     * @return
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbv
     */
    @ApiOperation(value = "订单退款查询", notes = "订单退款查询")
    @ApiOperationSupport(order = 2)
    @GetMapping("/refund/query/{tradeNum}")
    @ResponseBody
    public Object refundQuery(@PathVariable("tradeNum") String tradeNum) throws Exception {
        //创建API对应的request类
        AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
        JSONObject bizContent = new JSONObject();
        // 商户网站唯一订单号
        bizContent.put("out_trade_no", tradeNum);
        // 支付宝交易号
        // bizContent.put("trade_no", "2021081722001419121412730660");
        // 退款请求号,标识一次退款请求，需要保证在交易号下唯一，如需部分退款，则此参数必传。
        // 针对同一次退款请求，如果调用接口失败或异常了，重试时需要保证退款请求号不能变更。
        bizContent.put("out_request_no", "1122334455");

        request.setBizContent(bizContent.toString());
        AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);

        // 打印结果
        log.debug(response.getBody());

        // 根据response中的结果继续业务逻辑处理
        if (response.isSuccess()) {
            log.info("调用支付宝成功");
            log.info(response.getSubMsg());

            /**
             * 退款状态
             * REFUND_SUCCESS 退款处理成功；
             * 未返回该字段表示退款请求未收到或者退款失败；
             * 注：建议商户在退款发起后间隔10秒以上再发起退款查询请求。
             */
            String refundStatus = response.getRefundStatus();

            log.info("支付宝交易号：" + response.getTradeNo());
            log.info("商家订单号：" + response.getOutTradeNo());
            log.info("本笔退款对应的退款请求号：" + response.getOutRequestNo());
            log.info("该笔退款所对应的交易的订单金额：" + response.getTotalAmount());
            log.info("本次退款请求，对应的退款金额：" + response.getRefundAmount());
            log.info("退分账明细信息：" + response.getRefundRoyaltys());
            log.info("银行卡冲退信息：" + response.getDepositBackInfo());
        } else {
            log.error("调用支付宝失败");
            log.error(response.getSubCode());
            log.error(response.getSubMsg());
        }
        return "调用订单退款查询";
    }


    /**
     * @return
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivby
     */
    @ApiOperation(value = "收单退款冲退", notes = "收单退款冲退")
    @ApiOperationSupport(order = 2)
    @GetMapping("/completed")
    @ResponseBody
    public void completed(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("收单退款冲退");
        PrintWriter out = response.getWriter();
        //乱码解决，这段代码在出现乱码时使用
        request.setCharacterEncoding("utf-8");

        try {
            //获取支付宝GET过来反馈信息
            Map<String, String> params = new HashMap<>(8);
            Map<String, String[]> requestParams = request.getParameterMap();
            for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
                String[] values = stringEntry.getValue();
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                params.put(stringEntry.getKey(), valueStr);
            }

            // 比如：解析到的biz_content内容如下，此处代码因为无法通过银行卡退款测试，所以模拟已经解析除了biz_content值
            // biz_content={"trade_no":"2022021922001486510501693053","out_trade_no":"1645257277192","out_request_no":"HZ01RF001","dback_status":"S","dback_amount":"1.01","bank_ack_time":"2020-06-02 14:03:48","est_bank_receipt_time":"2020-06-02 14:03:48"}
            String str = "{\"trade_no\":\"2022021922001486510501693053\",\"out_trade_no\":\"1645257277192\",\"out_request_no\":\"HZ01RF001\",\"dback_status\":\"S\",\"dback_amount\":\"1.01\",\"bank_ack_time\":\"2020-06-02 14:03:48\",\"est_bank_receipt_time\":\"2020-06-02 14:03:48\"}";

            // 将biz_content值转为Map对象
            Map map = JSON.parseObject(str, Map.class);

            log.info("支付宝交易号：" + map.get("trade_no"));
            log.info("商家订单号：" + map.get("out_trade_no"));
            log.info("退款请求号：" + map.get("out_request_no"));
            // 银行卡冲退状态。S-成功，F-失败。银行卡冲退失败，资金自动转入用户支付宝余额
            log.info("银行卡冲退状态：" + map.get("dback_status"));
            log.info("银行卡冲退金额：" + map.get("dback_amount"));
            log.info("银行响应时间：" + map.get("bank_ack_time"));
            log.info("预估银行入账时间：" + map.get("est_bank_receipt_time"));
            out.print("success");
        } catch (Exception e) {
            out.print("fail");
        }
    }

    /**
     * @return
     * @throws Exception
     * @deprecated 具体API参数说明参考：https://opendocs.alipay.com/open/02ivbw
     */
    @ApiOperation(value = "查询对账单下载地址", notes = "查询对账单下载地址")
    @ApiOperationSupport(order = 2)
    @GetMapping("/bill")
    @ResponseBody
    public Object bill() throws Exception {
        //创建API对应的request类
        AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
        JSONObject bizContent = new JSONObject();
        /*
         * 账单类型，通过接口可以获取以下账单类型，支持：
         * trade：商户基于支付宝交易收单的业务账单；
         * signcustomer：基于商户支付宝余额收入及支出等资金变动的账务账单。
         */
        bizContent.put("bill_type", "trade");
        /*
         * 账单时间：
         * 日账单格式为yyyy-MM-dd，最早可下载2016年1月1日开始的日账单；
         * 月账单格式为yyyy-MM，最早可下载2016年1月开始的月账单。
         */
        bizContent.put("bill_date", "2016-04-05");

        request.setBizContent(bizContent.toString());
        AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);

        // 打印结果
        log.debug(response.getBody());

        // 根据response中的结果继续业务逻辑处理
        if (response.isSuccess()) {
            log.info("调用支付宝成功");
            log.info(response.getSubMsg());

            log.info("账单地址：" + response.getBillDownloadUrl());
            return response.getBillDownloadUrl();
        } else {
            log.error("调用支付宝失败");
            log.error(response.getSubCode());
            log.error(response.getSubMsg());
        }
        return "调用查询对账单下载地址";
    }
}
